﻿using Microsoft.Data.SqlClient;
using MyCompany.MyProject.Common.DB;
using MyCompany.MyProject.Common.Extensions;
using MyCompany.MyProject.Common.Helper;
using MyCompany.MyProject.Model.Models;
using MyCompany.MyProject.Model.Models.Tenants;
using Newtonsoft.Json;
using Serilog;
using SqlSugar;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;


#nullable disable

namespace MyCompany.MyProject.Common.DbSeed;

public class DBSeed
{
    #region 主库初始化

    private static string SeedDataFolder = "SeedData/{0}.tsv";

    /// <summary>
    /// 普通初始化数据库和数据
    /// </summary>
    /// <param name="myContext"></param>
    /// <param name="webRootPath"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static async Task SeedAsync(MyContext myContext, string webRootPath)
    {
        try
        {
            if (string.IsNullOrEmpty(webRootPath))
                throw new Exception("获取 wwwroot 路径时 异常");
            SeedDataFolder = Path.Combine(webRootPath, SeedDataFolder);
            Console.WriteLine("************ MyCompany.MyProject DataBase Set ************ ");
            Console.WriteLine($"Master DB ConId: {myContext.Db.CurrentConnectionConfig.ConfigId}");
            Console.WriteLine($"Master DB Type: {myContext.Db.CurrentConnectionConfig.DbType}");
            Console.WriteLine($"Master DB ConnectString: {myContext.Db.CurrentConnectionConfig.ConnectionString}");
            Console.WriteLine();
            if (BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException())
            {
                var index = 0;
                BaseDBConfig.MainConfig.SlaveConnectionConfigs.ForEach(m =>
                {
                    index++;
                    Console.WriteLine($"Slave{index} DB HitRate: {m.HitRate}");
                    Console.WriteLine($"Slave{index} DB ConnectString: {m.ConnectionString}");
                    Console.WriteLine($"--------------------------------------");
                });
            }
            else if (BaseDBConfig.ReuseConfigs.AnyNoException())
            {
                var index = 0;
                BaseDBConfig.ReuseConfigs.ForEach(m =>
                {
                    index++;
                    Console.WriteLine($"Reuse{index} DB ID: {m.ConfigId}");
                    Console.WriteLine($"Reuse{index} DB Type: {m.DbType}");
                    Console.WriteLine($"Reuse{index} DB ConnectString: {m.ConnectionString}");
                    Console.WriteLine($"--------------------------------------");
                });
            }

            Console.WriteLine();

            // 创建数据库
            Console.WriteLine($"Create Database(The Db Id:{MyContext.ConnId})...");

            if (MyContext.DbType != DbType.Dm && MyContext.DbType != DbType.Oracle)
            {
                myContext.Db.DbMaintenance.CreateDatabase();
                SqlConnection.ClearAllPools();
                ConsoleHelper.WriteSuccessLine($"Database created successfully!");
            }
            else
            {
                //Oracle 数据库不支持该操作
                ConsoleHelper.WriteSuccessLine($"Oracle 数据库不支持该操作，可手动创建Oracle/Dm数据库!");
            }
            // 创建数据库表，遍历指定命名空间下的class，
            // 注意不要把其他命名空间下的也添加进来。
            Console.WriteLine("Create Tables...");

            var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
            var referenceAssemblies = Directory.GetFiles(path, "MyCompany.MyProject.Model.dll")
                .Select(Assembly.LoadFrom).ToArray();
            var modelTypes = referenceAssemblies
                .SelectMany(a => a.DefinedTypes)
                .Select(type => type.AsType())
                .Where(x => x.IsClass && x.Namespace is "MyCompany.MyProject.Model.Models")
                .Where(i => !i.IsDefined(typeof(MultiTenantAttribute), false))
                .ToList();

            //modelTypes.ForEach(t =>
            //{
            //    if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name))
            //    {
            //        Console.WriteLine(t.Name);
            //        myContext.Db.CodeFirst.SplitTables().InitTables(t);
            //    }
            //});

            myContext.Db.CodeFirst.SplitTables().InitTables(modelTypes.ToArray());

            ConsoleHelper.WriteSuccessLine($"Tables created successfully!");
            Console.WriteLine();

            if (AppSettings.app(new string[] { "AppSettings", "SeedDBDataEnabled" }).ObjToBool())
            {
                JsonSerializerSettings setting = new JsonSerializerSettings();
                JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
                {
                    //日期类型默认格式化处理
                    setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
                    setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";

                    //空值处理
                    setting.NullValueHandling = NullValueHandling.Ignore;
                    return setting;
                });

                Console.WriteLine($"Seeding database data (The Db Id:{MyContext.ConnId})...");

                #region 初始化数据库

                await SeedTables<Modules>(myContext, setting, "Modules");

                await SeedTables<Permission>(myContext, setting, "Permission");

                await SeedTables<Role>(myContext, setting, "Role");

                await SeedTables<RoleModulePermission>(myContext, setting, "RoleModulePermission");

                await SeedTables<UserRole>(myContext, setting, "UserRole");

                await SeedTables<SysUserInfo>(myContext, setting, "SysUserInfo");

                await SeedTables<Department>(myContext, setting, "Department");

                #endregion

                //多租户种子初始化
                await SeedDataAsync(myContext.Db, (eType) => eType.GetCustomAttribute<MultiTenantAttribute>() is null);

                ConsoleHelper.WriteSuccessLine($"Done seeding database!");
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }

    /// <summary>
    /// 泛型方法用于个表初始化数据
    /// </summary>
    /// <typeparam name="T">要初始化数据表 model类</typeparam>
    /// <param name="myContext">上下文</param>
    /// <param name="settings">反序列化设置</param>
    /// <param name="tableName">表名称</param>
    /// <returns></returns>
    private static async Task SeedTables<T>(MyContext myContext, JsonSerializerSettings settings, string tableName) where T : class, new()
    {
        if (!await myContext.Db.Queryable<T>().AnyAsync())
        {
            var data = JsonConvert.DeserializeObject<List<T>>(FileHelper.ReadFile(string.Format(SeedDataFolder, tableName), Encoding.UTF8), settings);
            myContext.GetEntityDB<T>().InsertRange(data);
            ConsoleHelper.WriteSuccessLine($"Table:{tableName} Seed Success");
        }
        else
        {
            ConsoleHelper.WriteWarningLine("Table:Modules already exists...");
        }
    }

    #endregion

    #region 公用方法
    /// <summary>
    /// 种子初始化数据
    /// </summary>
    /// <param name="myContext"></param>
    /// <returns></returns>
    private static async Task SeedDataAsync(ISqlSugarClient db, Func<Type, bool> func)
    {
        // 获取所有种子配置-初始化数据
        var seedDataTypes = AssemblysExtensions.GetAllAssemblies().SelectMany(s => s.DefinedTypes)
           .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass)
           .Where(u =>
           {
               var esd = u.GetInterfaces().FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>)));
               if (esd is null)
                   return false;

               var eType = esd.GenericTypeArguments[0];
               return func(eType);
           });

        if (!seedDataTypes.Any()) return;
        foreach (var seedType in seedDataTypes)
        {
            dynamic instance = Activator.CreateInstance(seedType);
            //初始化数据
            {
                var seedData = instance.InitSeedData();
                if (seedData != null && Enumerable.Any(seedData))
                {
                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
                    var entity = db.EntityMaintenance.GetEntityInfo(entityType);

                    if (!await db.Queryable(entity.DbTableName, "").AnyAsync())
                    {
                        await db.Insertable(Enumerable.ToList(seedData)).ExecuteCommandAsync();
                        Console.WriteLine($"Table:{entity.DbTableName} init success!");
                    }
                }
            }

            //种子数据
            {
                var seedData = instance.SeedData();
                if (seedData != null && Enumerable.Any(seedData))
                {
                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
                    var entity = db.EntityMaintenance.GetEntityInfo(entityType);

                    await db.Storageable(Enumerable.ToList(seedData)).ExecuteCommandAsync();
                    Console.WriteLine($"Table:{entity.DbTableName} seedData success!");
                }
            }

            //自定义处理
            {
                await instance.CustomizeSeedData(db);
            }
        }
    }

    #endregion

    #region 初始化日志数据库
    /// <summary>
    /// 初始化日志数据库、表
    /// </summary>
    /// <param name="context"></param>
    /// <exception cref="ApplicationException"></exception>
    public static void MigrationLogs(MyContext context)
    {
        // 创建数据库表，指定命名空间下的类
        Console.WriteLine("Create Log Tables...");
        if (!context.Db.IsAnyConnection(SqlSugarConst.LogConfigId.ToLower()))
            throw new ApplicationException("未配置日志数据库，请在appsettings.json中DBS节点中配置");
        var logDb = context.Db.GetConnection(SqlSugarConst.LogConfigId.ToLower());
        Console.WriteLine($"Create log Database(The Db Id:{SqlSugarConst.LogConfigId.ToLower()})...");
        logDb.DbMaintenance.CreateDatabase();
        ConsoleHelper.WriteSuccessLine($"Log Database created successfully!");
        var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
        var referencedAssemblies = Directory.GetFiles(path, "MyCompany.MyProject.Model.dll")
            .Select(Assembly.LoadFrom).ToArray();
        var modelTyes = referencedAssemblies.SelectMany(a => a.DefinedTypes)
            .Select(t => t.AsType())
            .Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("MyCompany.MyProject.Model.Logs"))
            .ToList();

        var tables = logDb.DbMaintenance.GetTableInfoList();
        modelTyes.ForEach(t =>
        {
            if (!tables.Any(s => s.Name.Contains(t.Name)))
            {
                Console.WriteLine(t.Name);
                if (t.GetCustomAttribute<SplitTableAttribute>() != null)
                    logDb.CodeFirst.SplitTables().InitTables(t);
                else
                    logDb.CodeFirst.InitTables(t);
            }
        });
        ConsoleHelper.WriteSuccessLine("Log Tables Created Successfully!");
    } 
    #endregion

    #region 初始化多租户
    /// <summary>
    /// 初始化多租户
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public static async Task TenantSeedAsync(MyContext context)
    {
        // 获取分库多租户
        var tenants = await context.Db.Queryable<SysTenant>().Where(i => i.TenantType == TenantTypeEnum.Db).ToListAsync();

        if (tenants.Any())
        {
            Console.WriteLine($@"Init Multi Tenant Db");
            foreach (var tenant in tenants)
            {
                Console.WriteLine($@"Init Multi Tenant Db : {tenant.ConfigId}/{tenant.Name}");
                await InitTenantSeedAsync(context.Db.AsTenant(), tenant.GetConnectionConfig());
            }
        }
        tenants = await context.Db.Queryable<SysTenant>().Where(s => s.TenantType == TenantTypeEnum.Tables).ToListAsync();
        if (tenants.Any())
        {
            await InitTenantSeedAsync(context, tenants);
        }
    }

    /// <summary>
    /// 初始化多库
    /// </summary>
    /// <param name="itenant"></param>
    /// <param name="config"></param>
    /// <returns></returns>
    public static async Task InitTenantSeedAsync(ITenant itenant, ConnectionConfig config)
    {
        itenant.RemoveConnection(config.ConfigId);
        itenant.AddConnection(config);
        var db = itenant.GetConnectionScope(config.ConfigId);
        db.DbMaintenance.CreateDatabase();
        ConsoleHelper.WriteSuccessLine($"Init Multi Tenant Db : {config.ConfigId} Database created successfully!");
        Console.WriteLine($@"Init Multi Tenant Db : {config.ConfigId}  Create Tables");

        // 获取所有实体表-初始化租户业务表
        var entityTypes = TenantUtil.GetTenantEntityTypes(TenantTypeEnum.Db);
        if (!entityTypes.Any()) return;
        foreach (var entityType in entityTypes)
        {
            var splitTable = entityType.GetCustomAttribute<SplitTableAttribute>();
            if (splitTable == null)
                db.CodeFirst.InitTables(entityType);
            else
                db.CodeFirst.SplitTables().InitTables(entityType);

            Console.WriteLine(entityType.Name);
        }
        //多租户初始化种子数据
       // await TenantSeedDataAsync(db, TenantTypeEnum.Db);
        await SeedDataAsync(db, eType => eType.IsTenantEntity(TenantTypeEnum.Db));
    }

    /// <summary>
    /// 初始化分表多租户
    /// </summary>
    /// <param name="myContext"></param>
    /// <param name="tenants"></param>
    /// <returns></returns>
    private static async Task InitTenantSeedAsync(MyContext myContext, List<SysTenant> tenants)
    {
        ConsoleHelper.WriteInfoLine($"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId}");

        // 获取所有实体表-初始化租户业务表
        var entityTypes = TenantUtil.GetTenantEntityTypes(TenantTypeEnum.Tables);
        if (!entityTypes.Any()) return;

        foreach (var sysTenant in tenants)
        {
            foreach (var entityType in entityTypes)
            {
                var tableName = entityType.GetTenantTableName(myContext.Db, sysTenant.Id.ToString());
                myContext.Db.CodeFirst.As(entityType,tableName ).InitTables(entityType);

                Console.WriteLine($@"Init Tables:{tableName}");
            }

            myContext.Db.SetTenantTable(sysTenant.Id.ToString());
            //多租户初始化种子数据
            // await TenantSeedDataAsync(myContext.Db, TenantTypeEnum.Tables);
            await SeedDataAsync(myContext.Db, eType => eType.IsTenantEntity(TenantTypeEnum.Tables));
        }

        ConsoleHelper.WriteSuccessLine($"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId} created successfully!");
    }

    #endregion

    #region 初始SQLServer测试数据库
    /// <summary>
    /// 测试多数据库功能，需要在开发程序时候确定需要使用几个数据库
    /// </summary>
    /// <param name="myContext"></param>
    /// <exception cref="ApplicationException"></exception>
    public static void InitSqlServerTest(MyContext myContext)
    {
        Console.WriteLine("测试初始化多库 SQLServer数据库");
        if (!myContext.Db.IsAnyConnection(TestSqlServerDb.SqlServerDb.ToLower()))
            throw new ApplicationException("未配置多库，请在 appsettings.json 中 DB 节点中配置");

        var db = myContext.Db.GetConnection(TestSqlServerDb.SqlServerDb.ToLower());
        Console.WriteLine($"创建测试多数据库 SQLServer (The Db Id :{TestSqlServerDb.SqlServerDb.ToLower()})");
        db.DbMaintenance.CreateDatabase();
        ConsoleHelper.WriteSuccessLine("创建测试多数据库成功");
        var path = AppDomain.CurrentDomain.RelativeSearchPath??AppDomain.CurrentDomain.BaseDirectory;
        var referencedAssemblies = Directory.GetFiles(path, "MyCompany.MyProject.Model.dll")
            .Select(Assembly.LoadFrom).ToArray();
        var modelTypes = referencedAssemblies.SelectMany(a=>a.DefinedTypes)
            .Select(t=>t.AsType())
            .Where(x=>x.IsClass&&x.Namespace!=null&&x.Namespace.StartsWith("MyCompany.MyProject.Model.Models.SqlServerDBModel"))
            .ToList();

        db.CodeFirst.InitTables(modelTypes.ToArray());
        ConsoleHelper.WriteSuccessLine("多库SQLServer 库表创建成功");

    }

    #endregion
}
